简介
OpenGL
(Open Graphics Library)是个定义了一个跨编程语言、跨平台的编程接口,它用于二维或三维图象绘制。OpenGL是个专业的图形程序接口,是一个功能强大,调用方便的底层图形库。
OpenGL ES
OpenGL ES(Open Graphics Library for Embedded System)是一套为手持和嵌入式系统设计的2D/3D轻量图形库,它是基于OpenGL API设计的,是OpenGL三维图形API的一个子集。OpenGL ES是从OpenGL裁剪定制而来的,它去除了OpenGL中很多特性,并针对移动设备改善了图形显示效果,大大降低了内存消耗。
OpenGL ES渲染流程
在OpenGL ES中,任何事物都在3D空间中,而屏幕却是2D像素数组,这导致OpenGL ES的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。
3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。
图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
顶点输入
开始绘制图形之前,我们必须先给OpenGL ES输入一些顶点数据。OpenGL ES是一个3D图形库,所以我们在OpenGL ES中指定的所有坐标都是3D坐标(x、y和z)。
OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。
所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)。
标准化设备坐标:
标准化设备坐标(Normalized Device Coordinates, NDC)
一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):
与通常的屏幕坐标不同,y轴正方向为向上,(0, 0)坐标是这个图像的中心,而不是左上角。最终你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。
你的标准化设备坐标接着会变换为屏幕空间坐标(Screen-space Coordinates),这是使用你通过glViewport函数提供的数据,进行视口变换(Viewport Transform)完成的。所得的屏幕空间坐标又会被变换为片段输入到片段着色器中。
一组顶点数据:
1 | GLfloat vertexs[] = { |
Vertex Arrays / Buffer Objects
VBO(Vertex Buffer Objects):顶点缓存对象,用来管理和发送顶点数据到GPU中
VAO(Vertex Arrays Objects):顶点数组对象,它封装了与顶点处理器相关的所有数据。它没有包含实际的数据,而是引用顶点缓冲区、索引缓冲区和顶点本身。
Vertex Shader(顶点着色器)
处理顶点相关的数据,包括顶点在屏幕的位置(矩阵变换),顶点处的光照计算,纹理坐标等。
顶点着色器的信号图:
输入信号 Attributes、Uniforms、Samplers (optional)
Attributes:顶点属性,包括坐标,颜色,纹理等
Uniforms: 是一个全局常量,它可以被着色器程序的任意着色器在任意阶段访问
Samplers(可选的): 是一个特殊的 Uniforms 保存的是 Texteures(纹理) 数据;
输出信号: Varying、gl_Position、gl_FrontFacing、gl_PointSize
Varying:它是 Vertex Shader 与 Fragment Shader 的接口,是为了解决功能性问题(两个 Shader 的信息交互);
gl_Position(highp vec4 内建变量):就是 Vertex Position,Vertex Shader 的输出值,而且是必须要赋值的变量;只有在 Vertex Shader 中使用才会有效**;
gl_PointSize ( mediump float 内建变量):告诉 Vertex Shader 栅格化点的尺寸(pixels,像素化),想要改变绘制点的大小就是要用这个变量 只有在 Vertex Shader 中使用才会有效;
gl_FrontFacing ( bool 内建变量):改变渲染物体的 Front Facing 和 Back Facing , 是用于处理物体光照问题的变量,双面光照(3D 物体里外光照)问题的时候才会使用的变量,只能在 Vertex Shader 中进行设置, Fragment Shader 是只读的;
- 交互信息: Temporary Variables
- Temporary Variables:临时变量
Primitive Assembly(图元装配):
图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状
第一步,把 Vertex Shader 处理后的顶点数据组织成 OpenGL ES 可以直接渲染的基本图元:点、线、三角形;
第二步,裁剪 ( Clipping ) ,只保留在渲染区域(视锥体,视觉区域)内的图元;
第三步,剔除 ( Culling ),可通过编程决定剔除前面、后面、还是全部;
Rasterization ( 光栅化 ) :
光栅化信号图:
作用是,将基本图元(点、线、三角形)转换成二维的片元(Fragment, 包含二维坐标、颜色值、纹理坐标等等属性), 像素化基本图元使其可以在屏幕上进行绘制(显示)。
Texture Memory ( 纹理内存 ) :
Texture 就是指保存了图片(位图)的所有颜色的缓存;Texture Memory 就是图片的颜色(像素)内存;每一个嵌入式系统对 Texture Memory 的大小都是有限制的;
向上指向 Vertex Shader 的虚线,意指 Texture Coordinate (纹理坐标)信息是通过程序提供给它的;
向下指向Fragment Shader 的实线,Fragment Shader 处理的是光栅化后的数据,即像素数据,而 Texture 本身就是像素数据,所以 Texture Memory 可以直接当成 Fragment Shader 的输入;
Fragment Shader (片元着色器):
片元着色器信号图:
输入的内建变量
gl_FragCoord(mediump vec4 只读变量): 是保存窗口相对坐标的 { x, y, z, 1/w } 的变量,z 表示深度 (will be used for the fragment’s depth), w 表示旋转;
gl_PointCoord (mediump int 只读变量) :
是包含了当前片元原始点位置的二维坐标;点的范围是 [ 0, 1 ] ;
输出信号(内建变量):
- gl_FragColor(mediuump vec4):片元的颜色值
Per-Fragment Operations(每个片段操作):
信号图:
Pixel ownership test ( 像素归属测试 ) :
判断像素在 Framebuffer 中的位置是不是为当前 OpenGL ES Context 所有,即测试某个像素是否属于当前的 Context 或是否被展示(是否被用户可见);Scissor Test ( 裁剪测试 ) :
判断像素是否在由 glScissor* 定义的裁剪区域内,不在该剪裁区域内的像素就会被丢弃掉;Stencil Test ( 模版测试 ):
将模版缓存中的值与一个参考值进行比较,从而进行相应的处理;Depth Test ( 深度测试 ) :
比较下一个片段与帧缓冲区中的片段的深度,从而决定哪一个像素在前面,哪一个像素被遮挡;Blending ( 混合 ) :
将片段的颜色和帧缓存中已有的颜色值进行混合,并将混合所得的新值写入帧缓存 (FrameBuffer) ;Dithering ( 抖动 ) :
使用有限的色彩让你看到比实际图象更为丰富的色彩显示方式,以缓解表示颜色的值的精度不够大而导致颜色剧变的问题。
Render Buffer & Frame Buffer:
关系图:
Render Buffer( 渲染缓存 ):
- 简称 RBO , Render Buffer Object;
- 是由程序(Application)分配的 2D 图片缓存;
- Render Buffer 可以分配和存储颜色(color)、深度(depth)、模版(stectil)值,也可以把这三种值装载到 Frame Buffer 里面;
Frame Buffer ( 帧缓存 ):
- 简称 FBO , Frame Buffer Object;
- 是颜色、深度、模板缓存装载在 FBO 上所有装载点的合集;
- 描述颜色、深度、模板的大小和类型的属性状态;
- 描述 Texture 名称的属性状态;
- 描述装载在 FBO 上的 Render Buffer Objects ( 渲染缓存对象 ) 的属性状态;
扩充知识(FBO)
- 只能通过 OpenGL ES 命令 ( API ) 创建 FBO 对象;
- 使用一个 EGL Context 去创建和使用多个 FBO , 即不要为每一个 FBO 对象创建一个正在渲染的上下文(rendering context);
- 创建 off-screen 的颜色、深度、模板渲染缓存和纹理需要装载在 FBO 上;
- 通过多个 FBO 来共享颜色、深度、模板缓存;
- 正确地装载纹理的颜色或深度到 FBO 中,避免复制操作;
OpenGL ES在iOS中的应用
CAEAGLLayer
CAEAGLLayer类支持在iPhone应用程序中绘制OpenGL内容。
如果您打算使用OpenGL进行呈现,请使用这个类作为视图的支持层,从您的视图的layerClass类方法中返回它。
返回的CAEAGLLayer对象是一个与OpenGL ES函数调用完全兼容的核心动画表面的包装器。
1 | - (void)setupCAEAGLLayer { |
如果在viewController中,使用[self.view.layer addSublayer:eaglLayer];
如果在view中,可以直接重写UIView的layerClass类方法即可
1 | + (Class)layerClass { |
EAGLContext
EAGLContext对象管理着OpenGLES的渲染context,即所有绘制的状态,命令及资源信息,并控制GPU去执行渲染运算。
绘制如textures及renderbuffers的过程,是由一个与context绑定的EAGLSharegroup对象来管理的。当初始化一个EAGLContext对象的时候,可选择新建一个sharegroup,或者使用已有的,这一点我们往往采用系统默认即可。在绘制到context之前,我们要先绑定一个完整的framebuffer对象到context中。1
2_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; //选择OpenGLES API版本
[EAGLContext setCurrentContext:_eaglContext]; //设置为当前上下文。
RenderBuffer
RenderBuffer用于存储渲染的内容1
2
3
4
5
6
7
8
9
10
11
12- (void)setupRenderBuffer {
if (_colorRenderBuffer) {
glDeleteRenderbuffers(1, &_colorRenderBuffer);
_colorRenderBuffer = 0;
}
// 生成一个renderBuffer,id是_colorRenderBuffer
glGenRenderbuffers(1, &_colorRenderBuffer);
// 设置为当前renderBuffer,则后面引用GL_RENDERBUFFER,即指的是_colorRenderBuffer
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
//为color renderbuffer 分配存储空间
[_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
FrameBuffer
OpenGlES的FrameBuffer包含:renderBuffer,depthBuffer,stencilBuffer和accumulationBuffer。1
2
3
4
5
6
7
8
9
10
11
12
13- (void)setupFrameBuffer {
if (_frameBuffer) {
glDeleteFramebuffers(1, &_frameBuffer);
_frameBuffer = 0;
}
// FBO用于管理colorRenderBuffer,离屏渲染
glGenFramebuffers(1, &_frameBuffer);
//设置为当前framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
// 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}